package com.ruoyi.common.mybatisplus;

import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import org.apache.ibatis.type.EnumTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 自定义枚举映射类型处理
 *
 * <p>
 * 枚举类型映射，数据库中的字段与枚举类型的value属性对应, 如果没有的话，默认使用枚举的name来对应
 *
 * @param <E>
 */
public class EnumValueTypeHandler<E extends Enum<E>> extends EnumTypeHandler<E> {

    private final static String GET_VALUE_METHOD_NAME = "getValue";

    /**
     * 是否有value属性以及Getter方法
     */
    private boolean isValueEnum = false;
    private Method getValueMethod;
    private Map<String, E> enumValueMap;

    public EnumValueTypeHandler(Class<E> type) {
        super(type);
        init(type);
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
            throws SQLException {
        if (!isValueEnum) {
            super.setNonNullParameter(ps, i, parameter, jdbcType);
            return;
        }

        if (jdbcType == null) {
            ps.setObject(i, this.getValue(parameter));
        } else {
            ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
        }
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        if (!isValueEnum) {
            return super.getNullableResult(rs, columnName);
        }
        if (null == rs.getObject(columnName) && rs.wasNull()) {
            return null;
        }
        return this.valueOf(rs.getObject(columnName));
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        if (!isValueEnum) {
            return super.getNullableResult(rs, columnIndex);
        }
        if (null == rs.getObject(columnIndex) && rs.wasNull()) {
            return null;
        }
        return this.valueOf(rs.getObject(columnIndex));
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        if (!isValueEnum) {
            return super.getNullableResult(cs, columnIndex);
        }
        if (null == cs.getObject(columnIndex) && cs.wasNull()) {
            return null;
        }
        return this.valueOf(cs.getObject(columnIndex));
    }

    /**
     * 初始化
     *
     * @param type
     */
    private void init(Class<E> type) {
        for (Method declaredMethod : type.getDeclaredMethods()) {
            if (GET_VALUE_METHOD_NAME.equals(declaredMethod.getName()) &&
                    !Void.class.equals(declaredMethod.getReturnType())) {
                isValueEnum = true;
                getValueMethod = declaredMethod;
                break;
            }
        }

        if (isValueEnum) {
            enumValueMap = Arrays.stream(type.getEnumConstants()).collect(Collectors.toMap(e ->
                    {
                        try {
                            return String.valueOf(getValueMethod.invoke(e)).trim();
                        } catch (ReflectiveOperationException exception) {
                            throw ExceptionUtils.mpe(exception);
                        }
                    }, Function.identity()
            ));
        }
    }

    private Object getValue(E parameter) {
        try {
            return getValueMethod.invoke(parameter);
        } catch (ReflectiveOperationException e) {
            throw ExceptionUtils.mpe(e);
        }
    }

    private E valueOf(Object value) {
        return this.enumValueMap.get(String.valueOf(value).trim());
    }
}